/*
 * XResources.java, created on Oct 26, 2005
 * 
 * Copyright (c) 2005 - 2007
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.xfc.util;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.ResourceBundle;

import javax.swing.ImageIcon;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JToolBar;

import nu.xom.Builder;
import nu.xom.Document;
import nu.xom.Element;
import nu.xom.Elements;
import nu.xom.ParsingException;

import org.xfc.XApp;
import org.xfc.XComponentFactory;
import org.xfc.components.XDefaultAction;
import org.xfc.components.XProxyAction;
import org.xfc.util.platform.XPlatform;

// TODO: lazy parsing - keep the XOM in memory and parse as requested -


/**
 * Contains all the application resources - things like I18N strings, menubars and toolbars,
 * and icons.
 *
 * @author Devon Carew
 */
public class XResources
{
	private XApp app;
	
	private List intlResources = new ArrayList();
	
	private Map actions = new HashMap();
    //private Map macActions = new HashMap();
	private Map components = new HashMap();
	
	// TODO: this should be a weak hash map of some type
	private Map icons = new HashMap();
	
	/**
	 * 
	 * @param app
	 */
	public XResources(XApp app)
	{
		this.app = app;
	}
	
	/**
	 * @return a helper method to return the component factory for the app
	 */
	public XComponentFactory getComponentFactory()
	{
		return (XComponentFactory)app.getServiceLocator().getService(XComponentFactory.class);
	}
	
	/**
	 * @param bundle
	 */
	public void addInternationalizationResources(ResourceBundle bundle)
	{
		// cvt this to a map -
		Properties properties = new Properties();
		
		Enumeration keys = bundle.getKeys();
		
		while (keys.hasMoreElements())
		{
			String key = (String)keys.nextElement();
			
			properties.put(key, bundle.getObject(key));
		}
		
		intlResources.add(properties);
	}
	
	/**
	 * @param clazz
	 */
	public void addI18NResources(Class clazz)
	{
		addInternationalizationResources(ResourceBundle.getBundle(clazz.getName()));
	}
	
	/**
	 * @return the resource bundles that have been registered with this applciation
	 */
	public Properties[] getInternationalizationResources()
	{
		// TODO: or an array of properties?
		
		return (Properties[])intlResources.toArray(new Properties[intlResources.size()]);
	}

	/**
	 * Returns the I18N string for the given key.
	 * 
	 * @param key
	 * @return the I18N string for the given key, or <code>null</code> if the key was not found
	 */
	public String getString(String key)
	{
		return getString(key, null);
	}

	/**
	 * Given an icon's path (either a disk location or in the resource path) return the
	 * icon. If the icon cannot be found this method will return null. This class will
	 * cache icons - it will only ever load one copy of each icon.
	 * 
	 * @param path
	 *            a disk location or the resource path
	 * @return the icon if found; null if not
	 */
	public ImageIcon getIcon(String path)
	{
		ImageIcon icon = (ImageIcon)icons.get(path);
		
		if (icon != null)
			return icon;
		
		URL url = getClass().getResource(path);
		
		if (url == null)
			return null;
		
		icon = new ImageIcon(url);
		
		icons.put(path, icon);
		
		return icon;
	}
	
	/**
	 * Returns the I18N string for the given key.
	 * 
	 * @param key
	 * @param defaultValue
	 * @return the I18N string for the given key, or <code>defaultValue</code> if the key was not found
	 */
	public String getString(String key, String defaultValue)
	{
		for (int i = 0; i < intlResources.size(); i++)
		{
			Properties properties = (Properties)intlResources.get(i);
			
			if (properties.getProperty(key) != null)
				return properties.getProperty(key);
		}
		
		return defaultValue;
	}

	/**
	 * @param clazz
	 */
	public void addXMLResources(Class clazz)
	{
		try
		{
			addXMLResources(clazz.getResource(XUtils.getShortName(clazz) + ".xml"));
		}
		catch (IOException ioe)
		{
			throw new RuntimeException(ioe);
		}
        
        // Load in the platform specific overrides if they exist.
        try
        {
            URL url = clazz.getResource(XUtils.getShortName(clazz) + "_" + XPlatform.getShortPlatformId() + ".xml");
            
            if (url != null)
                addXMLResources(url);
        }
        catch (IOException ioe)
        {
            throw new RuntimeException(ioe);
        }
	}
	
	/**
	 * @param url
	 * @throws IOException
	 */
	public void addXMLResources(URL url)
		throws IOException
	{
		try
		{
			Document document = new Builder().build(url.openStream());
			
			Element root = document.getRootElement();
			
			if (!root.getLocalName().equals("resources"))
				throw new IOException("Document root must be <resources>");
			
			Elements elements = root.getChildElements();
			
			for (int i = 0; i < elements.size(); i++)
			{
				Element element = elements.get(i);
				
				if (element.getLocalName().equals("string"))
					parseString(element);
				else if (element.getLocalName().equals("menubar"))
					parseMenubar(element);
				else if (element.getLocalName().equals("menu"))
					parseMenu(element);
				else if (element.getLocalName().equals("toolbar"))
					parseToolbar(element);
                //else if (element.getLocalName().equals("macaction"))
                //    parseMacAction(element);
				else
					throw new IOException("Unrecognized element: <" + element.getLocalName() + ">");
			}
		}
		catch (ParsingException pe)
		{
			IOException ioe = new IOException(pe.getMessage());
			ioe.initCause(pe);
			throw ioe;
		}
	}
	
	/**
	 * @return the collection of actions defined
	 */
	public Collection getActions()
	{
		return actions.values();
	}
	
	/**
	 * @param command
	 * @return the action with the given name
	 */
	public XProxyAction getAction(String command)
	{
		XProxyAction action = (XProxyAction)actions.get(command);
		
		if (action == null)
		{
			action = getComponentFactory().createAction(command);
			
			getComponentFactory().applyAction(action);
			
			if (action.getText() == null)
				action.setText(action.getCommand());
			
			actions.put(action.getCommand(), action);
		}
		
		return action;
	}
	
//    /**
//     * Return the Java action associated with the given Macintosh specific action. The macActionName
//     * can be one of "handleAbout", "handleOpenApplication", "handleOpenFile", "handlePreferences",
//     * "handlePrintFile", and "handleQuit". When the user selects one of the Mac menu items, the
//     * associated Java action will be called. Enabling or disabling the Java action will enable and
//     * disable the Mac menu item.
//     * 
//     * @param macActionName
//     * @return
//     */
//    public XProxyAction getMacAction(String macActionName)
//    {
//        return (XProxyAction)macActions.get(macActionName);
//    }
    
	/**
	 * @param name
	 * @return the toolbar for the given name
	 */
	public JToolBar getToolBar(String name)
	{
		return (JToolBar)components.get(name);
	}
	
	/**
	 * @param name
	 * @return the menubar for the given name
	 */
	public JMenuBar getMenuBar(String name)
	{
		return (JMenuBar)components.get(name);
	}
	
	/**
	 * @param name
	 * @return the menu for the given name
	 */
	public JMenu getMenu(String name)
	{
		return (JMenu)components.get(name);
	}
	
	private void parseString(Element element)
	{
		// string := ( string ) *
		
		// TODO:
		
	}
	
	private JMenuBar parseMenubar(Element element)
		throws IOException
	{
		// menubar := ( menu ) *
		JMenuBar menubar = getComponentFactory().createMenuBar();
		
		menubar.setName(element.getAttributeValue("name"));
		
		Elements elements = element.getChildElements();
		
		for (int i = 0; i < elements.size(); i++)
		{
			Element e = elements.get(i);
			
			if (e.getLocalName().equals("menu"))
			{
				menubar.add(parseMenu(e));
			}
			else
			{
				throw new IOException("Unrecognized element: <" + e.getLocalName() + ">");
			}
		}
		
		if (menubar.getName() != null)
			components.put(menubar.getName(), menubar);
		
		return menubar;
	}
	
	private JMenu parseMenu(Element element)
		throws IOException
	{
		// menu := (action | menu | separator ) *
		JMenu menu = getComponentFactory().createMenu();
		
		menu.setName(element.getAttributeValue("name"));
		
		getComponentFactory().applyMenu(menu);
		
		if (menu.getText() == null || menu.getText().length() == 0)
			menu.setText(menu.getName());
		
		Elements elements = element.getChildElements();
		
		for (int i = 0; i < elements.size(); i++)
		{
			Element e = elements.get(i);
			
			if (e.getLocalName().equals("action"))
			{
				menu.add(parseAction(e));
			}
			else if (e.getLocalName().equals("menu"))
			{
				menu.add(parseMenu(e));
			}
			else if (e.getLocalName().equals("separator"))
			{
				menu.addSeparator();
			}
			else
			{
				throw new IOException("Unrecognized element: <" + e.getLocalName() + ">");
			}
		}
		
		if (menu.getName() != null)
			components.put(menu.getName(), menu);
		
		return menu;
	}
	
	private XDefaultAction parseAction(Element element)
	{
		// string := ( string ) *
		XDefaultAction action = getAction(element.getAttributeValue("name"));
		
		if (action == null)
		{
			action = getComponentFactory().createAction(element.getAttributeValue("name"));
			
			getComponentFactory().applyAction(action);
			
			if (action.getText() == null)
				action.setText(action.getCommand());
			
			actions.put(action.getCommand(), action);
		}
		
		return action;
	}
	
	private JToolBar parseToolbar(Element element)
		throws IOException
	{
		// toolbar := ( action | separator ) *
		JToolBar toolbar = getComponentFactory().createToolBar();
		
		toolbar.setName(element.getAttributeValue("name"));
		
		Elements elements = element.getChildElements();
		
		for (int i = 0; i < elements.size(); i++)
		{
			Element e = elements.get(i);
			
			if (e.getLocalName().equals("action"))
			{
				toolbar.add(parseAction(e));
			}
			else if (e.getLocalName().equals("separator"))
			{
				toolbar.addSeparator();
			}
			else
			{
				throw new IOException("Unrecognized element: <" + e.getLocalName() + ">");
			}
		}
		
		if (toolbar.getName() != null)
			components.put(toolbar.getName(), toolbar);
		
		return toolbar;
	}
	
//    private XDefaultAction parseMacAction(Element element)
//        throws IOException
//    {
//        String macActionName = element.getAttributeValue("name");
//        String javaActionName = element.getAttributeValue("action");
//        
//        XDefaultAction action = getAction(javaActionName);
//        
//        macActions.put(macActionName, action);
//        
//        return action;
//    }
    
}
